package com.liuwa.social.weixin.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.liuwa.common.core.redis.RedisCache;
import com.liuwa.common.utils.StringUtils;
import com.liuwa.common.utils.spring.SpringUtils;
import com.liuwa.social.exception.WeixinAccessTokenException;
import com.liuwa.social.weixin.enums.QrcodeActionName;
import com.liuwa.social.weixin.model.Menu;
import com.liuwa.social.weixin.vo.*;
import com.liuwa.social.http.WeixinHttpClient;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 微信工具类
 * @author rubekid
 *
 * 2017年10月14日 下午7:33:50
 */
public class WeixinUtils {
	
	private static final Logger logger = LoggerFactory.getLogger(WeixinUtils.class);
	
	/**
	 * 缓存前缀
	 */
	private static final String WEIXIN_CACHE_PREFIX = "weixin";
	
	/**
	 * 获取公众号token
	 */
	private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
	
	/**
	 * 获取公众号票据
	 */
	private static final String TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi";
	
	/**
	 * 获取tokenURL
	 */
	private static final String USER_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
	
	/**
	 * 获取用户信息URL
	 */
	private static final String USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
	
	/**
	 * 授权登录
	 */
	private static final String AUTHORIZE_URL  = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect";
	
	/**
	 * 获取粉丝信息
	 */
	private static final String FANS_USER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN";
	
	/**
	 * 二维码
	 */
	private static final String CREATE_QRCODE = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s";

	/**
	 * 无限二维码
	 */
	private static final String CREATE_QRCODE_UNLIMIT = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s";

	/**
	 * 菜单
	 */
	private static final String CREATE_MENU = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s";

	private static RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
	
	/**
	 * 通过code换取网页授权access_token
	 * @param appId
	 * @param appSecret
	 * @param code
	 */
	public static WeixinAccessToken getUserAccessToken(String appId, String appSecret, String code){
		String url = String.format(USER_ACCESS_TOKEN_URL, appId, appSecret, code);
		return WeixinHttpClient.getForObject(url, WeixinAccessToken.class);
	}
	

	/**
	 * 获取用户信息
	 * @param accessToken
	 * @param openid
	 */
	public static WeixinUser getUserInfo(String accessToken, String openid){
		String url = String.format(USERINFO_URL, accessToken, openid);
		return WeixinHttpClient.getForObject(url, WeixinUser.class);
	}
	
	
	/**
	 * 获取Token
	 * @param appId
	 * @param appSecret
	 * @return
	 */
	public static AccessToken getAccessToken(String appId, String appSecret){
		return getAccessToken(appId, appSecret, true);
	}

	/**
	 * 获取Token
	 * @param appId
	 * @param appSecret
	 * @param useCache
	 * @return
	 */
	public static AccessToken getAccessToken(String appId, String appSecret, boolean useCache){
		String key = accessTokenKey(appId);
		AccessToken accessToken = null;
		if(useCache){
			accessToken = redisCache.getCacheObject(key);
		}

		logger.debug("AccessToken：{}", JSON.toJSONString(accessToken));
		if(accessToken == null){
			String url = String.format(ACCESS_TOKEN_URL, appId, appSecret);
			accessToken =  WeixinHttpClient.getForObject(url, AccessToken.class);
			accessToken.setExpireAt(new Date(System.currentTimeMillis() + accessToken.getExpiresIn() * 1000));
			redisCache.setCacheObject(key, accessToken, accessToken.getExpiresIn(), TimeUnit.SECONDS);

			logger.debug("新获取到AccessToken：{}", JSON.toJSONString(accessToken));
		}
		return accessToken;
	}

	/**
	 * 删除token
	 * @param appId
	 */
	public static void removeAccessToken(String appId){
		String key = accessTokenKey(appId);
		redisCache.remove(key);
	}
	
	/**
	 * 获取票据
	 * @param accessToken
	 * @return
	 */
	public static Ticket getTicket(String accessToken){
		String key = ticketKey(accessToken);
		Ticket ticket = redisCache.getCacheObject(key);
		if(ticket == null){
			String url = String.format(TICKET_URL, accessToken);
			ticket =  WeixinHttpClient.getForObject(url, Ticket.class);
			ticket.setExpireAt(new Date(System.currentTimeMillis() + ticket.getExpiresIn() * 1000));
			redisCache.setCacheObject(key, ticket, ticket.getExpiresIn());
		}
		return ticket;
	}

	/**
	 * 获取微信签名
	 * @param appId
	 * @param secret
	 * @param url
	 * @return
	 */
	public static WxJsConfig signature(String appId, String secret, String url){
		WxJsConfig wxJsConfig = new WxJsConfig();
		wxJsConfig.setAppId(appId);
		wxJsConfig.setNonceStr(StringUtils.getRandomString(16));
		wxJsConfig.setTimestamp(System.currentTimeMillis() / 1000);
		AccessToken accessToken = getAccessToken(appId, secret);
		Ticket ticket = getTicket(accessToken.getAccessToken());
		String str = String.format("jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s", ticket.getTicket(), wxJsConfig.getNonceStr(), wxJsConfig.getTimestamp(),url);
		String signature = DigestUtils.shaHex(str.getBytes());
		wxJsConfig.setSignature(signature);
		return wxJsConfig;
	}
	

	
	/**
	 * 授权登录
	 * @param response
	 * @param appId
	 * @param redirectUrl
	 * @throws IOException
	 */
	public static void authorize(HttpServletResponse response, String appId, String redirectUrl) throws IOException{
		String location = String.format(AUTHORIZE_URL, appId, redirectUrl);
		response.sendRedirect(location);
	}
	
	/**
	 * 获取粉丝用户信息
	 * @param accessToken
	 * @param openid
	 */
	public static FansUserInfo getFansUserInfo(String accessToken, String openid){
		String url = String.format(FANS_USER_INFO_URL, accessToken, openid);
		return WeixinHttpClient.getForObject(url, FansUserInfo.class);
	}
	

	/**
	 * 获取粉丝信息
	 * @param openid
	 * @return
	 */
	public static FansUserInfo getFansUserInfo(String appId, String appSecret, String openid){
		AccessToken accessToken = getAccessToken(appId, appSecret);
		return getFansUserInfo(accessToken.getAccessToken(), openid);
	}
	

	
	/**
	 * 生成二维码
	 * @param appId
	 * @param appSecret
	 * @param actionName
	 * @param sceneId
	 * @param expireSeconds
	 * @return
	 */
	public static QrcodeResponse createQrcode(String appId, String appSecret, QrcodeActionName actionName, String sceneId, Integer expireSeconds){
		try{
			AccessToken accessToken = getAccessToken(appId, appSecret);
			return createQrcode(accessToken, actionName, sceneId, expireSeconds);
		}
		catch (WeixinAccessTokenException ex){
			removeAccessToken(appId);
			throw ex;
		}

	}
	
	/**
	 * 生成二维码
	 * @param accessToken AccessToken
	 * @param actionName
	 * @param sceneId
	 * @param expireSeconds
	 * @return
	 */
	public static QrcodeResponse createQrcode(AccessToken accessToken, QrcodeActionName actionName, String sceneId, Integer expireSeconds){
		JSONObject scene = new JSONObject();
		if(StringUtils.isNumeric(sceneId)){
			scene.put("scene_id", Long.parseLong(sceneId));
		}
		else{
			scene.put("scene_str", sceneId);
		}
		JSONObject body = new JSONObject();
		JSONObject actionInfo = new JSONObject();
		actionInfo.put("scene", scene);
		body.put("action_name", actionName);
		if((actionName.equals(QrcodeActionName.QR_SCENE)
				|| actionName.equals(QrcodeActionName.QR_STR_SCENE))
			&& expireSeconds != null){
			body.put("expire_seconds", expireSeconds);
		}
		body.put("action_info", actionInfo);

		String url = String.format(CREATE_QRCODE, accessToken.getAccessToken());
		logger.info("url :{}", url);
		return WeixinHttpClient.postForObject(url, body, QrcodeResponse.class);
	}

	/**
	 * 生成二维码（无限量）
	 * @param accessToken
	 * @param scene
	 * @param page
	 * @param width
	 * @param autoColor
	 * @param lineColor
	 * @param hyaline
	 * @return
	 */
	public static byte[] createQrcodeUnlimit(AccessToken accessToken, String scene, String page, Integer width, Boolean autoColor, Object lineColor, Boolean hyaline){

		JSONObject body = new JSONObject();

		body.put("scene", scene);

		if(page != null){
			body.put("page", page);
		}

		if(width != null){
			body.put("width", width);
		}

		if(autoColor != null){
			body.put("auto_color", autoColor);
		}

		if(lineColor != null){
			body.put("line_color", lineColor);
		}

		if(hyaline != null){
			body.put("is_hyaline", hyaline);
		}


		String url = String.format(CREATE_QRCODE_UNLIMIT, accessToken.getAccessToken());
		return WeixinHttpClient.postForByte(url, body);
	}

	/**
	 * 生成二维码（无限量）
	 * @param accessToken
	 * @param scene
	 * @param page
	 * @return
	 */
	public static byte[] createQrcodeUnlimit(AccessToken accessToken, String scene, String page){
		return createQrcodeUnlimit(accessToken,scene, page, null, null, null, null);
	}

	/**
	 * 生成二维码（无限量）
	 * @param accessToken
	 * @param scene
	 * @param page
	 * @param hyaline
	 * @return
	 */
	public static byte[] createQrcodeUnlimit(AccessToken accessToken, String scene, String page, Boolean  hyaline){
		return createQrcodeUnlimit(accessToken,scene, page, null, null, null, hyaline);
	}

	/**
	 * 生成二维码（无限量）
	 * @param appId
	 * @param appSecret
	 * @param scene
	 * @param page
	 * @param hyaline
	 * @return
	 */
	public static byte[] createQrcodeUnlimit(String appId, String appSecret, String scene, String page, Boolean  hyaline){
		AccessToken accessToken = getAccessToken(appId, appSecret);
		byte[] bytes = createQrcodeUnlimit(accessToken, scene, page, hyaline);
		if(bytes.length < 1024){
			String content = new String(bytes);
			logger.error("生成失败 {}", content);
			if(content.contains("access_token is invalid")){
				accessToken = getAccessToken(appId, appSecret, false);
				bytes = createQrcodeUnlimit(accessToken, scene, page, hyaline);
			}
		}

		return bytes;
	}

	

	/**
	 * 创建菜单
	 * @param menu
	 * @param appId
	 * @param appSecret
	 */
	public static boolean createMenu(Menu menu, String appId, String appSecret) {
		AccessToken accessToken = getAccessToken(appId, appSecret);
		String url = String.format(CREATE_MENU, accessToken.getAccessToken());
		String content = WeixinHttpClient.post(url, JSONObject.parseObject(JSON.toJSONString(menu)));
		logger.info("响应内容：{}", content);
		if(content.startsWith("{") && content.endsWith("}")) {
			JSONObject response = JSONObject.parseObject(content);
			if(response.getInteger("errcode") == 0) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Access Token 缓存key
	 * @param appId
	 * @return
	 */
	public static String accessTokenKey(String appId){
		return String.format("%s:%s:%s", WEIXIN_CACHE_PREFIX, "access_token", appId);
	}
	
	/**
	 * 票据 缓存key
	 * @param accessToken
	 * @return
	 */
	public static String ticketKey(String accessToken){
		return String.format("%s:%s:%s", WEIXIN_CACHE_PREFIX, "ticket", accessToken);
	}
	
	

}